package com.inspect;


import ilog.concert.*;
import ilog.cplex.IloCplex;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

public class cplex {
    IloCplex model;
    double cost;
    private IloNumVar[][][] x;
    public int x_count=0;
    public int y_count=0;
    private IloNumVar[] y;
    private  ArrayList<int[]> setO;//o、q、j，
    private  ArrayList<int[]> goalJ;//目标矩形块所对应索引、价值、次数
    private  ArrayList<int[]> allJ;//所有可能矩形块
    private long runTime;
    private ArrayList<int[]> LocationAndSize;
    private data data;
    private IloRange[] range;
    HashMap<Integer, Double> DualsValue=new HashMap<>();
    long endTime;

    public long getRunTime() {
        return runTime;
    }

    public ArrayList<int[]> getLocationAndSize() {
        return LocationAndSize;
    }

    public cplex(data data1) {
        this.data = data1;
    }

    public void runModel(int reductionsMethod) throws IloException {
        buildModel(reductionsMethod);
        if (!model.solve()) {
            System.out.println("problem should not solve false!!!");
        } else {
            endTime = System.currentTimeMillis();
            cost = model.getObjValue();
            System.out.println("final solution:"+cost);

//            for (int[] jValue:goalJ){//goal:目标矩形块所对应索引、价值、次数
//                if (jValue[0]!=0) {
//                    System.out.println(jValue[0]+"对应y值为"+ model.getValue(y[jValue[0]])+"其价值为"+jValue[1]);
//                }
//            }
//
            HashMap<Integer,int[]> result_Xindex = new HashMap<>();//索引，【方向，切割距离，切割次数】
            for (int[] index : setO) {
                int xValue = (int) model.getValue(x[index[0]][index[1]][index[2]]);
                if (xValue != 0) {//获取切割方式
//                    System.out.println("x"+index[0]+","+index[1]+","+index[2]+":"+xValue);
                    result_Xindex.put(index[2],new int[]{index[0], index[1], xValue});
                }
            }
            LocationAndSize=getResult(result_Xindex);
//

//            for (int[] list : goalJ){
//                System.out.println(list[0]+"对应y值为"+ model.getValue(y[list[0]]));
//            }
//            System.out.println();
//            System.out.println();

//            for (int j = 0; j < range.length; j++) {
//                IloRange r=range[j];
//                if (r!=null){
//                    DualsValue.put(j,model.getDual(r));
//                }
//            }
        }
        model.end();
    }

    private void buildModel(int reductionsMethod) throws IloException {
        model = new IloCplex();
        setO=data.getSetO();
        goalJ=data.getGoalJ();
        allJ=data.getAllJ();
        range=new IloRange[allJ.size()];
        if (reductionsMethod==1){
            reductionsMethod1(model);
        }
        else {
            reductionsMethod0(model);
        }
        model.exportModel("model.lp");
    }

    private void reductionsMethod1(IloCplex model) throws IloException {
        IloNumExpr expr1 = model.linearNumExpr();
        y = new IloIntVar[allJ.size()];
        for (int[] jValue:goalJ){  //goal:目标矩形块所对应索引、价值、次数
            y[jValue[0]]=model.intVar(0,Integer.MAX_VALUE,"y"+jValue[0]);
            y_count++;
//            model.addLe(y[jValue[0]], jValue[2]);// 添加约束（ 5、7 ）
            model.addLe(y[jValue[0]], 1);//受限
            if (jValue[1]!=0){
                expr1 =model.sum(expr1,model.prod(y[jValue[0]],jValue[1]));
            }
        }
        model.addMaximize(expr1);//添加目标函数（1）

        x=new IloIntVar[2][Math.max(allJ.get(0)[0],allJ.get(0)[1])][allJ.size()];
        for (int[] list:setO){//setO为可能切割
            x[list[0]][list[1]][list[2]]=model.intVar(0,Integer.MAX_VALUE,"x"+list[0]+list[1]+"_"+list[2]);
            x_count++;
        }//(6)

        IloNumExpr expr4=model.linearNumExpr();
        for (int[] list:setO){
            if (list[2]==0){
                expr4=model.sum(expr4,x[list[0]][list[1]][list[2]]);
            }
        }
        model.addLe(model.sum(expr4,y[0]),1);//（4）

        //(2)
        HashSet<Integer> goalIndex=new HashSet<>();
        for (int[] goal:goalJ) {
            goalIndex.add(goal[0]);
        }//获取目标矩形块对应索引
        goalIndex.remove(0);
        for (int index :goalIndex) {
            IloNumExpr expr2_2 =model.linearNumExpr();
            IloNumExpr expr2_1= model.linearNumExpr();
            for (int[] list:setO){
                if (list[2]==index) {
                    expr2_2 = model.sum(expr2_2, x[list[0]][list[1]][list[2]]);
                }
            }
            for (int[] list1 :setO){
                expr2_1= model.sum(expr2_1, model.prod(x[list1[0]][list1[1]][list1[2]],aJudge(list1[0], list1[1], list1[2],index,allJ)));
            }
            range[index]= (IloRange) model.addGe(model.sum(expr2_1, model.prod(-1,expr2_2)),y[index]);
        }

        //（3）
        for (int j = 1; j < allJ.size(); j++) {
            if (!goalIndex.contains(j)){
                IloNumExpr expr3_2= model.linearNumExpr();
                IloNumExpr expr3_1= model.linearNumExpr();
                for (int[] listK:setO){
                    expr3_1= model.sum(expr3_1,model.prod(x[listK[0]][listK[1]][listK[2]],aJudge(listK[0],listK[1],listK[2],j,allJ)));
                }
                for (int[] list : setO){
                    if (j ==list[2]){
                        expr3_2= model.sum(expr3_2,x[list[0]][list[1]][j]);
                    }
                }
                range[j]=model.addGe(model.sum(expr3_1, model.prod(-1,expr3_2)),0);
            }
        }
    }
    private void reductionsMethod0(IloCplex model) throws IloException {
        IloNumExpr expr1 = model.linearNumExpr();
        y = new IloNumVar[allJ.size()];
        for (int[] jValue:goalJ){
            y[jValue[0]]=model.numVar(0,Integer.MAX_VALUE,"y"+jValue[0]);
            y_count++;
            model.addLe(y[jValue[0]], jValue[2]);// 添加约束（ 5、7 ）
            if (jValue[1]!=0){
                expr1 =model.sum(expr1,model.prod(y[jValue[0]],jValue[1]));
            }
        }
        model.addMaximize(expr1);//添加目标函数（1）

        x=new IloNumVar[2][Math.max(allJ.get(0)[0],allJ.get(0)[1])][allJ.size()];
        for (int[] list:setO){//setO为可能切割
            x[list[0]][list[1]][list[2]]=model.numVar(0,Integer.MAX_VALUE,"x"+list[0]+list[1]+"_"+list[2]);
            x_count++;
        }//(6)

        IloNumExpr expr4=model.linearNumExpr();
        for (int[] list:setO){
            if (list[2]==0){
                expr4=model.sum(expr4,x[list[0]][list[1]][list[2]]);
            }
        }
        model.addLe(model.sum(expr4,y[0]),1);//（4）

        //(2)
        HashSet<Integer> goalIndex=new HashSet<>();
        for (int[] goal:goalJ) {
            goalIndex.add(goal[0]);
        }//获取目标矩形块对应索引
        goalIndex.remove(0);
        for (int index :goalIndex) {
            IloNumExpr expr2_2 =model.linearNumExpr();
            IloNumExpr expr2_1= model.linearNumExpr();
            for (int[] list:setO){
                if (list[2]==index) {
                    expr2_2 = model.sum(expr2_2, x[list[0]][list[1]][list[2]]);
                }
            }
            for (int[] list1 :setO){
                expr2_1= model.sum(expr2_1, model.prod(x[list1[0]][list1[1]][list1[2]],aJudge(list1[0], list1[1], list1[2],index,allJ)));
            }
            model.addGe(model.sum(expr2_1, model.prod(-1,expr2_2)),y[index]);
        }

        //（3）
        for (int j = 1; j < allJ.size(); j++) {
            if (!goalIndex.contains(j)){
                IloNumExpr expr3_2= model.linearNumExpr();
                IloNumExpr expr3_1= model.linearNumExpr();
                for (int[] listK:setO){
                    expr3_1= model.sum(expr3_1,model.prod(x[listK[0]][listK[1]][listK[2]],aJudge(listK[0],listK[1],listK[2],j,allJ)));
                }
                for (int[] list : setO){
                    if (j ==list[2]){
                        expr3_2= model.sum(expr3_2,x[list[0]][list[1]][j]);
                    }
                }
                model.addGe(model.sum(expr3_1, model.prod(-1,expr3_2)),0);
            }
        }
    }

    //HashMap<Integer,int[]>indexX：索引，【方向，切割距离，切割次数】
    public ArrayList<int[]> getResult(HashMap<Integer,int[]>indexX)  { //{j--o/q/num}
        List<ArrayList<antNode>> locations=new ArrayList<>();
        antNode first=new antNode(0,0,new int[]{0,0},allJ.get(0));
        ArrayList<antNode> tmp=new ArrayList<>();
        tmp.add(first);
        locations.add(tmp);
        List<ArrayList<antNode>> result=iter(locations,indexX);
        ArrayList<int[]> resultLocationAndSize=new ArrayList<>();
        for (ArrayList<antNode> innerList : result) {
            for (antNode node : innerList) {
               // System.out.println(node.index+"("+node.LAndW[0]+","+node.LAndW[1]+")"+"--"+ Arrays.toString(node.location));
                for (int[]list:goalJ){
                    if (list[0]==node.index){
                        resultLocationAndSize.add(new int[]{node.location[0],node.location[1],
                                node.LAndW[0],node.LAndW[1]});
                    }
                }
            }
        }
        return resultLocationAndSize;
    }


    //HashMap<Integer,int[]>indexX：索引，【方向，切割距离，切割次数】
    private List<ArrayList<antNode>> iter(List<ArrayList<antNode>> locations,HashMap<Integer,int[]>indexX){
        ArrayList<antNode> tmpIndex=locations.get(locations.size()-1);//获取第n次切割所获得的索引集合
        ArrayList<antNode> nextIndex=new ArrayList<>();
        int[] rec1;int[] rec2;
        for (antNode node:tmpIndex){
            if(indexX.containsKey(node.index)) {
                int[] cutMethod = indexX.get(node.index);//o/q/num
                if (cutMethod.length == 0) {
                    break;
                }
                if (cutMethod[2] == 0) {
                    break;//切割次数为0
                } else {
                    if (cutMethod[0] == 1) {
                        int newW = allJ.get(node.index)[1];
                        int newL1 = cutMethod[1];
                        int newL2 = allJ.get(node.index)[0] - cutMethod[1];
                        rec1 = new int[]{newL1, newW};
                        rec2 = new int[]{newL2, newW};
                        int index1 = findIndex(rec1);
                        int index2 = findIndex(rec2);
                        antNode node1 = new antNode(node.index, index1, node.location,rec1);
                        nextIndex.add(node1);
                        antNode node2 = new antNode(node.index, index2, new int[]{node.location[0] + cutMethod[1], node.location[1]},rec2);
                        nextIndex.add(node2);
                    } else {
                        int newL = allJ.get(node.index)[0];
                        int newW1 = cutMethod[1];
                        int newW2 = allJ.get(node.index)[1] - cutMethod[1];
                        rec1 = new int[]{newL, newW1};
                        rec2 = new int[]{newL, newW2};
                        int index1 = findIndex(rec1);
                        int index2 = findIndex(rec2);
//                    if(indexX.containsKey(index1)){
                        antNode node1 = new antNode(node.index, index1, node.location,rec1);
                        nextIndex.add(node1);
//                    }
//                    if(indexX.containsKey(index2)){
                        antNode node2 = new antNode(node.index, index2, new int[]{node.location[0], node.location[1] + cutMethod[1]},rec2);
                        nextIndex.add(node2);
//                    }
                    }
                    cutMethod[2]--;
                    indexX.put(node.index, cutMethod);
                }
            }
        }
        if (nextIndex.isEmpty()){return locations;}
        locations.add(nextIndex);
        iter(locations,indexX);
        return locations;
    }
    private int findIndex(int[] target) {
        for (int i = 1; i < allJ.size(); i++) {
            if (allJ.get(i)[0]==target[0]&&allJ.get(i)[1]==target[1]) {
                return i;
            }
        }
        return -1; // 如果未找到，返回 -1
    }



    //切k所得j的块数
    private static double aJudge(int o, int q, int k, int j, ArrayList<int[]> cutSet_Copy) {
        int[] k_set = cutSet_Copy.get(k);
        int[] j_set = cutSet_Copy.get(j);
        if (o == 1) {
            if (j_set[1] == k_set[1] && q == j_set[0] && (k_set[0] - q) == j_set[0]) return 2.0;
            if (j_set[1] == k_set[1] && (q == j_set[0] || (k_set[0] - q) == j_set[0])) return 1.0;
        }else {
            if (k_set[0] == j_set[0] && q == j_set[1] && (k_set[1] - q) == j_set[1]) return 2.0;
            if (k_set[0] == j_set[0] && (q == j_set[1] || (k_set[1] - q) == j_set[1])) return 1.0;
        }
        return 0.0;
    }
}